package com._119yn.host.gst200.service.impl;

import com._119yn.host.gst200.cansocket.message.CanFrame;
import com._119yn.host.gst200.constant.CanDatum;
import com._119yn.host.gst200.constant.HostStatuses;
import com._119yn.host.gst200.constant.LogTags;
import com._119yn.host.gst200.database.dao.DeviceTypeDao;
import com._119yn.host.gst200.database.entity.DeviceStatus;
import com._119yn.host.gst200.database.entity.DeviceType;
import com._119yn.host.gst200.database.entity.HostSession;
import com._119yn.host.gst200.database.entity.PointTableRecord;
import com._119yn.host.gst200.model.*;
import com._119yn.host.gst200.mqtt.MqttMessageReceiver;
import com._119yn.host.gst200.properties.DtuProperties;
import com._119yn.host.gst200.service.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.messaging.Message;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class DtuServiceImpl implements DtuService, MqttMessageReceiver {

    private final DtuProperties properties;

    private final ObjectMapper objectMapper;

    private final DeviceTypeDao deviceTypeDao;

    private final HostService hostService;

    private final DadsService dadsService;

    private final PointTableService pointTableService;

    private final Map<Integer, Map<Integer, List<Signal>>> hostSequenceSignals = new HashMap<>();

    @Getter
    @Setter
    private boolean deviceRegistered = false;

    @Async
    @Override
    public void startDeviceRegister() {
        while (!isDeviceRegistered()) {
            try {
                if (pointTableService.isImported()) {
                    log.info("{}:[设备注册]: 设备注册开始", LogTags.getDtuServer());
                    registers();
                    setDeviceRegistered(true);
                    log.info("{}:[设备注册]: 设备注册成功", LogTags.getDtuServer());
                    break;
                } else {
                    log.info("{}:[设备注册]: 点表尚未导入", LogTags.getDtuServer());
                }
            } catch (Exception e) {
                log.warn("{}:[设备注册]: 注册设备失败,原因: {}", LogTags.getDtuServer(), e.getMessage());
            }
            try {
                Thread.sleep(10 * 1000);
            } catch (InterruptedException ignored) {
            }
        }
    }

    @Async
    @Scheduled(fixedRate = 10 * 1000, initialDelay = 10 * 1000)
    @Synchronized
    @Override
    public void startPushHostAndDtuConnectStatus() {
        if (properties.isDeviceRegisterEnabled() && !isDeviceRegistered()) {
            return;
        }
        List<HostSession> hostSessions = hostService.getAllHostSessions();
        if (hostSessions.isEmpty()) {
            return;
        }
        for (HostSession hostSession : hostSessions) {
            if (hostSession.getOffline() == null) {
                continue;
            }
            if (hostSession.getOffline() == 1) {
                hostOffline(hostSession);
            } else {
                hostKeepalive(hostSession);
            }
        }
        if (hostService.isAllHostSessionOffline()) {
            dtuDataAcquisitionAbnormal();
        } else {
            dtuKeepalive();
        }
    }

    @Async
    @Scheduled(fixedRate = 30 * 1000, initialDelay = 11 * 1000)
    @Synchronized
    @Override
    public void startPushHostStatusTask() {
        if (properties.isDeviceRegisterEnabled() && !isDeviceRegistered()) {
            return;
        }
        List<HostSession> hostSessions = hostService.getOnlineHostSessions();
        List<ZngkHostActionSignal> actionSignals = new LinkedList<>();
        List<ZngkHostStatusSignal> statusSignals = new LinkedList<>();
        for (HostSession hostSession : hostSessions) {
            if (hostSession.getSilence() != null) {
                ZngkHostActionSignal actionSignal = new ZngkHostActionSignal();
                actionSignal.setDevId(hostSession.getDeviceId());
                actionSignal.setDevAddress(hostSession.getDeviceAddress());
                actionSignal.setTypeKey(hostSession.getDeviceType());
                actionSignal.getAttr().setSilence(hostSession.getSilence());
                actionSignals.add(actionSignal);
            }
            if (hostSession.getManualSta() != null || hostSession.getAutoSta() != null || hostSession.getSpraySta() != null || hostSession.getGuardSta() != null) {
                ZngkHostStatusSignal statusSignal = new ZngkHostStatusSignal();
                statusSignal.setDevId(hostSession.getDeviceId());
                statusSignal.setDevAddress(hostSession.getDeviceAddress());
                statusSignal.setTypeKey(hostSession.getDeviceType());
                statusSignal.getAttr().setManualSta(hostSession.getManualSta());
                statusSignal.getAttr().setAutoSta(hostSession.getAutoSta());
                statusSignal.getAttr().setSpraySta(hostSession.getSpraySta());
                statusSignal.getAttr().setGuardSta(hostSession.getGuardSta());
                statusSignals.add(statusSignal);
            }
        }
        if (!actionSignals.isEmpty()) {
            dadsService.update(createPlatformSignal(actionSignals));
        }
        if (!statusSignals.isEmpty()) {
            dadsService.update(createPlatformSignal(statusSignals));
        }
    }

    @Async
    @Scheduled(fixedRate = 60 * 1000, initialDelay = 12 * 1000)
    @Synchronized
    @Override
    public void startPushDeviceStatusTask() {
        if (properties.isDeviceRegisterEnabled() && !isDeviceRegistered()) {
            return;
        }
        List<DeviceStatus> deviceStatusList = hostService.getAllDeviceStatus();
        List<ZngkDevSignal> signals = new LinkedList<>();
        for (DeviceStatus deviceStatus : deviceStatusList) {
            PointTableRecord pointTableRecord = pointTableService.getByDeviceId(deviceStatus.getDeviceId());
            ZngkDevSignal signal = new ZngkDevSignal();
            signal.setDevId(pointTableRecord.getDeviceId());
            signal.setDevAddress(pointTableRecord.getDeviceAddress());
            signal.setTypeKey(pointTableRecord.getDeviceType());
            signal.getAttr().setAlarm(deviceStatus.getAlarm());
            signal.getAttr().setFault(deviceStatus.getFault());
            signal.getAttr().setAction(deviceStatus.getAction());
            signal.getAttr().setStartup(deviceStatus.getStartup());
            signal.getAttr().setShield(deviceStatus.getShield());
            signals.add(signal);
        }
        if (signals.isEmpty()) {
            return;
        }
        dadsService.update(createPlatformSignal(signals));
    }

    @Synchronized
    @Override
    public void register(CanFrameSender sender) {
        hostService.register(sender);
    }

    @Synchronized
    @Override
    public void onCanDataReceived(CanFrame canFrame) {
        if (isValid(canFrame)) {
            log.info("无效数据: {}", canFrame.getLineData());
            return;
        }

        Signal signal = createSignal(canFrame.getLineData());

        // 不处理应答/响应/辅助信号
        if (signal.getCall() != 1) {
            return;
        }

        // 未导入点表前不进行处理
        if (!pointTableService.isImported()) {
            logWarn(signal, "数据被丢弃，原因: 点表尚未导入或导入了空的点表");
            return;
        }

        String dtuId = getDtuId();
        if (dtuId == null) {
            logWarn(signal, "数据被丢弃，原因: 点表中未含有DTU的相关记录");
            return;
        }

        HostSession hostSession = hostService.getHostSessionByHostId(signal.getHost());
        if (hostSession == null) {
            logWarn(signal, "数据被丢弃，原因: 点表中未含有信号来源主机的相关记录");
            return;
        }

        // 激活主机会话
        hostService.active(hostSession, signal);

        List<Signal> signals = getSequenceSignals(hostSession.getHostId(), signal.getSequence());

        // 单包数据
        if (signal.getMark() == 0) {
            if (!signals.isEmpty()) {
                // 清理上一轮遗留的数据
                signals.clear();
            }
            signals.add(signal);
            handlePacketData(hostSession, signals);
            signals.clear();
        }
        // 分包数据
        else {
            // 加入当前帧
            signals.add(signal);
            // 加入当前帧后数据变得不合法说明已存在的数据和当前数据并非属于同一个包，丢弃已经存在的数据，保留当前帧
            if (!isPacketDataValid(signals)) {
                signals.clear();
                signals.add(signal);
            }
            // 判断包数据是否完整，如果已经完整立即处理整包数据
            if (isPacketDataCompleted(signals)) {
                handlePacketData(hostSession, signals);
                signals.clear();
            }
        }
    }


    @Override
    @Synchronized
    public void onMqttMessage(Message<?> message) {
        if (message == null) {
            return;
        }
        try {
            Object payload = message.getPayload();
            if (ObjectUtils.isEmpty(payload)) {
                log.warn("{}:[没有内容]: {}", LogTags.getMqttReceiver(), payload);
                return;
            }
            String data;
            if (payload instanceof byte[] bytes) {
                data = new String(bytes);
            } else if (payload instanceof String string) {
                data = string;
            } else {
                log.warn("{}:[无法解析]: {}", LogTags.getMqttReceiver(), payload);
                return;
            }
            String dtuId = getDtuId();
            if (dtuId == null) {
                log.warn("{}:[DTU_ID未配置]: {}", LogTags.getMqttReceiver(), data);
                return;
            }
            ZngkControlCommand command;
            try {
                command = objectMapper.readValue(data, ZngkControlCommand.class);
            } catch (IOException e) {
                log.warn("{}:[解析错误]: {}", LogTags.getMqttReceiver(), data);
                return;
            }
            if (command == null) {
                log.warn("{}:[指令为空]: {}", LogTags.getMqttReceiver(), data);
                return;
            }
            if (!dtuId.equals(command.getDtuId())) {
                log.warn("{}:[与我无关]: {}", LogTags.getMqttReceiver(), data);
                return;
            }
            if (command.getAttr() == null) {
                log.warn("{}:[没有操作]: {}", LogTags.getMqttReceiver(), data);
                return;
            }

            // 获取主机会话
            HostSession hostSession = hostService.getHostSessionByDeviceId(command.getDevId(), false);
            if (hostSession == null) {
                log.info("{}:[主机掉线]: {}", LogTags.getMqttReceiver(), data);
                return;
            }

            // 获取主机号
            int hostId = hostSession.getHostId();

            // 每次只允许进行一个动作
            if (command.getAttr().getSilenceSta() != null && command.getAttr().getSilenceSta() == 1) {
                log.info("{}:[主机消音]: {}", LogTags.getMqttReceiver(), data);
                hostService.hostSilence(hostId);
            } else if (command.getAttr().getResSta() != null && command.getAttr().getResSta() == 1) {
                log.info("{}:[主机复位]: {}", LogTags.getMqttReceiver(), data);
                hostService.hostReset(hostId);
            } else if (command.getAttr().getManualSta() != null) {
                if (command.getAttr().getManualSta() == 1) {
                    log.info("{}:[手动允许]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostManualPermit(hostId);
                } else {
                    log.info("{}:[手动禁止]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostManualForbid(hostId);
                }
            } else if (command.getAttr().getAutoSta() != null) {
                if (command.getAttr().getAutoSta() == 1) {
                    log.info("{}:[全部自动]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostAutoFull(hostId);
                } else if (command.getAttr().getAutoSta() == 2) {
                    log.info("{}:[部分自动]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostAutoPartial(hostId);
                } else {
                    log.info("{}:[禁止自动]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostAutoForbid(hostId);
                }
            } else if (command.getAttr().getSpraySta() != null) {
                if (command.getAttr().getSpraySta() == 1) {
                    log.info("{}:[喷洒允许]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostSprayPermit(hostId);
                } else {
                    log.info("{}:[喷洒禁止]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostSprayForbid(hostId);
                }
            } else if (command.getAttr().getGuardSta() != null) {
                if (command.getAttr().getGuardSta() == 1) {
                    log.info("{}:[防盗开启]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostGuardEnable(hostId);
                } else {
                    log.info("{}:[防盗关闭]: {}", LogTags.getMqttReceiver(), data);
                    hostService.hostGuardDisable(hostId);
                }
            } else if (command.getAttr().getStartup() != null) {
                if (command.getAttr().getStartup() == 1) {
                    log.info("{}:[启动设备]: {}", LogTags.getMqttReceiver(), data);
                    hostService.deviceStartup(command.getDevId());
                } else {
                    log.info("{}:[停止设备]: {}", LogTags.getMqttReceiver(), data);
                    hostService.deviceStartupStop(command.getDevId());
                }
            } else if (command.getAttr().getShield() != null) {
                if (command.getAttr().getShield() == 1) {
                    log.info("{}:[屏蔽设备]: {}", LogTags.getMqttReceiver(), data);
                    hostService.deviceShield(command.getDevId());
                } else {
                    log.info("{}:[取消屏蔽]: {}", LogTags.getMqttReceiver(), data);
                    hostService.deviceShieldCancel(command.getDevId());
                }
            } else {
                log.warn("{}:[无效操作]: {}", LogTags.getMqttReceiver(), message.getPayload());
            }
        } catch (Exception e) {
            log.warn("{}:[处理异常]: {}", LogTags.getMqttReceiver(), message.getPayload(), e);
        }
    }

    @Override
    public void onMqttErrorMessage(Message<?> message) {
        if (message == null) {
            return;
        }
        log.info("onHostControlError: {}", message.getPayload());
    }

    @Override
    public void importPointTable() throws Exception {
        pointTableService.importPointTable();
    }

    private List<Signal> getSequenceSignals(int host, int sequence) {
        return hostSequenceSignals.computeIfAbsent(host, k -> new HashMap<>()).computeIfAbsent(sequence, k -> new LinkedList<>());
    }

    private void dtuDataAcquisitionAbnormal() {
        processDtuDataAcquisitionAbnormal();
    }

    private void dtuKeepalive() {
        processDtuKeepalive();
    }

    private void hostOffline(HostSession hostSession) {
        processHostOffline(hostSession);
    }

    private void hostKeepalive(HostSession hostSession) {
        processHostKeepalive(hostSession);
    }

    /**
     * DTU采集异常
     */
    private void processDtuDataAcquisitionAbnormal() {
        PointTableRecord pointTableRecord = pointTableService.getDtu();
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(null, null);
            return;
        }

        ZngkDtuSignal s = new ZngkDtuSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(getDtuDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setDataAcquisition(1);
        s.getAttr().setNetworkCommunications(1);

        dtuNetworkState(null, List.of(s));
    }

    /**
     * DTU保活
     */
    private void processDtuKeepalive() {
        PointTableRecord pointTableRecord = pointTableService.getDtu();
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(null, null);
            return;
        }

        ZngkDtuSignal s = new ZngkDtuSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(getDtuDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setDataAcquisition(0);
        s.getAttr().setNetworkCommunications(0);

        dtuNetworkState(null, List.of(s));
    }

    /**
     * 主机离线
     *
     * @param session 主机会话
     */
    private void processHostOffline(HostSession session) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), session.getHostId() + "", session.getDeviceType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, null);
            return;
        }

        ZngkHostSignal s = new ZngkHostSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setAlarmHostOffline(1);

        dtuNetworkState(session, List.of(s));
    }

    /**
     * 主机保活
     *
     * @param session 主机会话
     */
    private void processHostKeepalive(HostSession session) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), session.getHostId() + "", session.getDeviceType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, null);
            return;
        }

        ZngkHostSignal s = new ZngkHostSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setAlarmHostOffline(0);

        dtuNetworkState(session, List.of(s));
    }

    private void processHostTimeSync(HostSession session, Signal signal) {

    }

    private void processDebugMode(HostSession session, Signal signal) {

    }

    private void processFaultClear(HostSession session, Signal signal) {

    }

    private void processHostReset(HostSession session, Signal signal) {
        // 所有在线主机都受到影响
        hostService.changeOnlineHostToSilence(0);

        // 清空所有设备的状态数据
        hostService.cleanDeviceStatus();

        List<HostSession> hostSessions = hostService.getOnlineHostSessions();

        List<Integer> hostIds = new LinkedList<>();
        List<ZngkHostActionSignal> actionSignals = new LinkedList<>();
        List<ZngkHostActionSignal> actionRecoverSignals = new LinkedList<>();

        for (HostSession hostSession : hostSessions) {
            hostIds.add(hostSession.getHostId());
            {
                ZngkHostActionSignal s = new ZngkHostActionSignal();
                s.setDevId(hostSession.getDeviceId());
                s.setDevAddress(hostSession.getDeviceAddress());
                s.setTypeKey(hostSession.getDeviceType());
                s.getAttr().setResSta(1);
                actionSignals.add(s);
            }
            {
                ZngkHostActionSignal s = new ZngkHostActionSignal();
                s.setDevId(hostSession.getDeviceId());
                s.setDevAddress(hostSession.getDeviceAddress());
                s.setTypeKey(hostSession.getDeviceType());
                s.getAttr().setResSta(0);
                actionRecoverSignals.add(s);
            }
        }

        update(session, signal, actionSignals);
        update(session, signal, actionRecoverSignals);

        List<ZngkDevSignal> deviceSignals = new LinkedList<>();
        List<PointTableRecord> pointTableRecords = pointTableService.getDeviceByPanel(hostIds);
        for (PointTableRecord pointTableRecord : pointTableRecords) {
            ZngkDevSignal s = new ZngkDevSignal();
            s.setDevId(pointTableRecord.getDeviceId());
            s.setDevAddress(pointTableRecord.getDeviceAddress());
            s.setTypeKey(pointTableRecord.getDeviceType());
            s.getAttr().setAlarm(0);
            s.getAttr().setFault(0);
            s.getAttr().setAction(0);
            s.getAttr().setStartup(0);
            s.getAttr().setShield(0);
            deviceSignals.add(s);
        }

        update(session, signal, deviceSignals);
    }

    private void processHostSilence(HostSession session, Signal signal) {
        // 所有在线主机都受到影响
        hostService.changeOnlineHostToSilence(1);

        List<HostSession> hostSessions = hostService.getOnlineHostSessions();
        List<ZngkHostActionSignal> signals = new LinkedList<>();
        for (HostSession hostSession : hostSessions) {
            ZngkHostActionSignal s = new ZngkHostActionSignal();
            s.setDevId(hostSession.getDeviceId());
            s.setDevAddress(hostSession.getDeviceAddress());
            s.setTypeKey(hostSession.getDeviceType());
            s.getAttr().setSilence(1);
            signals.add(s);
        }

        update(session, signal, signals);
    }

    private void processHostStatus(HostSession session, Signal signal) {

        long hostStatus = signal.getStatus();
        boolean manualForbid = (hostStatus & HostStatuses.MANUAL_MASK) == HostStatuses.MANUAL_FORBID;
        boolean autoForbid = (hostStatus & HostStatuses.AUTO_MASK) == HostStatuses.AUTO_FORBID;
        boolean sprayForbid = (hostStatus & HostStatuses.SPRAY_MASK) == HostStatuses.SPRAY_FORBID;
        boolean guardDisable = (hostStatus & HostStatuses.GUARD_MASK) == HostStatuses.GUARD_DISABLE;

        // 所有在线主机都受到影响
        hostService.changeOnlineHostStatuses(hostStatus, manualForbid ? 0 : 1, autoForbid ? 0 : 1, sprayForbid ? 0 : 1, guardDisable ? 0 : 1);

        List<HostSession> hostSessions = hostService.getOnlineHostSessions();
        List<ZngkHostStatusSignal> signals = new LinkedList<>();
        for (HostSession hostSession : hostSessions) {
            ZngkHostStatusSignal s = new ZngkHostStatusSignal();
            s.setDevId(hostSession.getDeviceId());
            s.setDevAddress(hostSession.getDeviceAddress());
            s.setTypeKey(hostSession.getDeviceType());
            s.getAttr().setManualSta(manualForbid ? 0 : 1);
            s.getAttr().setAutoSta(autoForbid ? 0 : 1);
            s.getAttr().setSpraySta(sprayForbid ? 0 : 1);
            s.getAttr().setGuardSta(guardDisable ? 0 : 1);
            signals.add(s);
        }

        update(session, signal, signals);
    }

    /**
     * 启动信号
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processStartup(HostSession session, Signal signal) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setStartup(1);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setStartup(1);

        update(session, signal, List.of(s));
    }

    /**
     * 停动信号
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processStartupStop(HostSession session, Signal signal) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setStartup(0);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setStartup(0);

        update(session, signal, List.of(s));
    }

    /**
     * 屏蔽信号
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processShield(HostSession session, Signal signal) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setShield(1);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setShield(1);

        update(session, signal, List.of(s));
    }

    /**
     * 屏蔽取消
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processShieldCancel(HostSession session, Signal signal) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setShield(0);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setShield(0);

        update(session, signal, List.of(s));
    }

    /**
     * 火警信号
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processAlarm(HostSession session, Signal signal) {
        // 所有在线主机都受到影响
        hostService.changeOnlineHostToSilence(0);

        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setAlarm(1);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setAlarm(1);

        update(session, signal, List.of(s));
    }

    /**
     * 反馈信号
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processAction(HostSession session, Signal signal) {
        // 所有在线主机都受到影响
        hostService.changeOnlineHostToSilence(0);

        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setAction(1);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setAction(1);

        update(session, signal, List.of(s));
    }

    /**
     * 反馈终止
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processActionTerminate(HostSession session, Signal signal) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setAction(0);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setAction(0);

        update(session, signal, List.of(s));
    }

    /**
     * 故障信号
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processFault(HostSession session, Signal signal) {
        // 所有在线主机都受到影响
        hostService.changeOnlineHostToSilence(0);

        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setFault(1);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setFault(1);

        update(session, signal, List.of(s));
    }

    /**
     * 故障恢复
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void processFaultRecover(HostSession session, Signal signal) {
        PointTableRecord pointTableRecord = pointTableService.getByPanelAndDeviceAddressAndDeviceType(session.getHostId(), signal.getDevCode(), signal.getDevType());
        // 点表未定义的数据不处理
        if (pointTableRecord == null) {
            logPointTableRecordNotFound(session, signal);
            return;
        }

        DeviceStatus deviceStatus = hostService.getDeviceStatusByDeviceId(pointTableRecord.getDeviceId(), true);
        deviceStatus.setFault(0);
        deviceStatus.setLastUpdate(LocalDateTime.now());

        hostService.saveDeviceStatus(deviceStatus);

        ZngkDevSignal s = new ZngkDevSignal();
        s.setDevId(pointTableRecord.getDeviceId());
        s.setDevAddress(pointTableRecord.getDeviceAddress());
        s.setTypeKey(pointTableRecord.getDeviceType());
        s.getAttr().setFault(0);

        update(session, signal, List.of(s));
    }

    private void processUnknown(HostSession session, Signal signal) {

    }

    private void registers() {
        List<PointTableRecord> pointTableRecords = pointTableService.getAll();
        if (pointTableRecords == null || pointTableRecords.isEmpty()) {
            throw new UnsupportedOperationException("点表尚未导入无法进行设备注册");
        }

        List<ZngkDevRegisterSignal> signals = new LinkedList<>();
        for (PointTableRecord pointTableRecord : pointTableRecords) {
            ZngkDevRegisterSignal s = new ZngkDevRegisterSignal();
            s.setDevId(pointTableRecord.getDeviceId());
            s.setDevAddress(pointTableRecord.getDeviceAddress());
            s.setTypeKey(pointTableRecord.getDeviceType());
            s.setDevText(pointTableRecord.getText());
            signals.add(s);
        }

        dadsService.registers(createPlatformSignal(signals));
    }

    void dtuNetworkState(HostSession hostSession, List<? extends ZngkSignal> signals) {
        if (!isDeviceRegistered()) {
            return;
        }
        dadsService.dtuNetworkState(createPlatformSignal(signals));
    }

    void update(HostSession session, Signal signal, List<? extends ZngkSignal> signals) {
        if (!isDeviceRegistered()) {
            return;
        }
        dadsService.update(createPlatformSignal(signals));
    }

    private String getDtuId() {
        return properties.getDtuId();
    }

    private String getDtuName() {
        return properties.getDtuName();
    }

    private String getDtuDeviceId() {
        return pointTableService.getDtuDeviceId();
    }

    private String getDtuDeviceAddress() {
        return pointTableService.getDtuDeviceAddress();
    }

    private ZngkPlatformSignal createPlatformSignal(List<? extends ZngkSignal> signals) {
        ZngkPlatformSignal platformSignal = new ZngkPlatformSignal();
        platformSignal.setDtuId(getDtuId());
        platformSignal.setDtuName(getDtuName());
        platformSignal.setAppId("app");
        platformSignal.setSign("0");
        platformSignal.setTime(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
        platformSignal.setSignals(signals);
        return platformSignal;
    }

    private boolean isValid(CanFrame frame) {
        return frame != null &&
                frame.getCanId() != null &&
                frame.getCanData() != null &&
                !frame.getCanId().matches("^[0-9A-F]{8}$") &&
                !frame.getCanData().matches("^[0-9A-F]{16}$");
    }

    private boolean isPacketDataValid(List<Signal> frames) {
        // 没有数据
        if (frames.isEmpty()) {
            return true;
        }
        // 已有数据
        else {
            // 首包MARK必定不为0
            if (frames.size() == 1) {
                return frames.getFirst().getMark() > 0;
            }
            // 从第二包到最后一包的MARK是递增的
            else {
                for (int i = 1; i < frames.size(); i++) {
                    if (frames.get(i).getMark() != i) {
                        return false;
                    }
                }
                return true;
            }
        }
    }

    private boolean isPacketDataCompleted(List<Signal> signal) {
        return signal.size() > 1 && signal.getFirst().getMark() == signal.getLast().getMark();
    }

    private void handlePacketData(HostSession session, List<Signal> signals) {
        Signal firstSignal = signals.getFirst();
        // 分包数据处理
        switch (firstSignal.getCode()) {
            // 火警信号包
            // CODE=0xC1 11000001 XP => 0x18200000 >>> 21
            // CODE=0x82 10000010
            // CODE=0x41 01000001
            case 0x41 -> {
                // 火警信号
                firstSignal.setName("火警信号");
                firstSignal.setText(getEventCommonDescription(firstSignal.getCanData()));
                firstSignal.setDevCode(getCanDataDevCode(firstSignal.getCanData()));
                firstSignal.setDevType(getCanDataDevType(firstSignal.getCanData()));
                firstSignal.setDevTypeName(getCanDataDevTypeName(firstSignal.getDevType()));

                // 火警数据为2个数据包
                for (int i = 1; i < signals.size(); i++) {
                    Signal signal = signals.get(i);
                    signal.setName("火警数据");
                    signal.setText(getDataCommonDescription(signal.getCanData()));
                }

                // 整合分包数据
                String s1 = signals.get(1).getText();
                String s2 = signals.get(2).getText();
                String ss = s1.concat(s2);
                firstSignal.setExtText(ss);

                logInfo(firstSignal, "火警信号[%s]，火警数据[%s]", firstSignal.getText(), firstSignal.getExtText());

                processAlarm(session, firstSignal);
            }
            // 反馈信号包
            // CODE=0xC2 11000010 XP => 0x18400000 >>> 21
            // CODE=0x84 10000100
            // CODE=0x42 00100111
            case 0x42 -> {
                // 反馈信号
                firstSignal.setName("反馈信号");
                firstSignal.setText(getEventCommonDescription(firstSignal.getCanData()));
                firstSignal.setDevCode(getCanDataDevCode(firstSignal.getCanData()));
                firstSignal.setDevType(getCanDataDevType(firstSignal.getCanData()));
                firstSignal.setDevTypeName(getCanDataDevTypeName(firstSignal.getDevType()));

                // 反馈数据为2个数据包
                for (int i = 1; i < signals.size(); i++) {
                    Signal signal = signals.get(i);
                    signal.setName("反馈数据");
                    signal.setText(getDataCommonDescription(signal.getCanData()));
                }

                // 整合分包数据
                String s1 = signals.get(1).getText();
                String s2 = signals.get(2).getText();
                String ss = s1.concat(s2);
                firstSignal.setExtText(ss);

                logInfo(firstSignal, "反馈信号[%s]，反馈数据[%s]", firstSignal.getText(), firstSignal.getExtText());

                processAction(session, firstSignal);
            }
            // 故障信号包
            // CODE=0xE1 11100001 XP => 0x1C200000 >>> 21
            // CODE=0xC2 11000010
            // CODE=0x61 01100001
            case 0x61 -> {
                // 故障信号
                firstSignal.setName("故障信号");
                firstSignal.setText(getEventCommonDescription(firstSignal.getCanData()));
                firstSignal.setDevCode(getCanDataDevCode(firstSignal.getCanData()));
                firstSignal.setDevType(getCanDataDevType(firstSignal.getCanData()));
                firstSignal.setDevTypeName(getCanDataDevTypeName(firstSignal.getDevType()));

                // 故障数据为2个数据包
                for (int i = 1; i < signals.size(); i++) {
                    Signal signal = signals.get(i);
                    signal.setName("故障数据");
                    signal.setText(getDataCommonDescription(signal.getCanData()));
                }

                // 整合分包数据
                String s1 = signals.get(1).getText();
                String s2 = signals.get(2).getText();
                String ss = s1.concat(s2);
                firstSignal.setExtText(ss);

                logInfo(firstSignal, "故障信号[%s]，故障数据[%s]", firstSignal.getText(), firstSignal.getExtText());

                processFault(session, firstSignal);
            }
            default -> {
                // 单包数据处理
                for (Signal signal : signals) {
                    handleSignal(session, signal);
                }
            }
        }
    }

    /**
     * 单包数据处理
     *
     * @param session 主机会话
     * @param signal  信号对象
     */
    private void handleSignal(HostSession session, Signal signal) {
        switch (signal.getCode()) {
            // 主机复位
            // CODE=0xA5 11000001 XP => 0x14A00000 >>> 21
            // CODE=0x4A 01001010
            // CODE=0x4A 01001010
            case 0x25 -> {
                signal.setName("主机复位");
                signal.setText("--");

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processHostReset(session, signal);
            }
            // 消音信号
            // CODE=0xA6 10100110 XP => 0x14C00000 >>> 21
            // CODE=0x4C 01001100
            // CODE=0x26 00100110
            case 0x26 -> {
                signal.setName("消音信号");
                signal.setText("--");

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processHostSilence(session, signal);
            }
            // 主机状态
            // CODE=0xA7 10100111 XP => 0x14E00000 >>> 21
            // CODE=0x4E 01001110
            // CODE=0x27 00100111
            case 0x27 -> {
                signal.setName("主机状态");
                signal.setText(getEventStatusDescription(signal.getCanData()));
                signal.setStatus(getEventStatusValue(signal.getCanData()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processHostStatus(session, signal);
            }
            // 调试模式/故障消除
            // CODE=0xAE 10101110 XP => 15C00000 >>> 21
            // CODE=0x5C 01011100
            // CODE=1x2E 00101110
            case 0x2E -> {// 从机故障消除和进入调试模式都是这一个,进入调试模式时所有数据均为0
                if (CanDatum.CAN_ZERO_DATA.equals(signal.getCanData())) {
                    // 按系统设置按钮输入正确的密码（默认: 24220001）
                    signal.setName("调试模式");
                    signal.setText("--");

                    logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                    processDebugMode(session, signal);

                } else {
                    // 按系统设置按钮输入空密码后从【3.网络设置】中选择【6.故障从机清除】
                    // 貌似从机故障消除后会上传一次主机状态（不稳定，所以不取）
                    signal.setName("故障消除");
                    signal.setText("--");

                    logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                    processFaultClear(session, signal);
                }

            }
            // 火警信号
            // CODE=0xC1 11000001 XP => 0x18200000 >>> 21
            // CODE=0x82 10000010
            // CODE=0x41 01000001
            case 0x41 -> {
                signal.setName("火警信号");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processAlarm(session, signal);
            }
            // CODE=0x82 10000010 XP => 0x10400000 >>> 21
            // CODE=0x04 00000100
            // CODE=0x02 00000010
            case 0x02 -> {
                signal.setName("火警数据");
                signal.setText(getDataCommonDescription(signal.getCanData()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());
            }
            // 反馈信号
            // CODE=0xC2 11000010 XP => 0x18400000 >>> 21
            // CODE=0x84 10000100
            // CODE=0x42 00100111
            case 0x42 -> {
                signal.setName("反馈信号");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processAction(session, signal);
            }
            // 反馈数据
            // CODE=0x83 11000010 XP => 0x10600000 >>> 21
            // CODE=0x06 00000110
            // CODE=0x03 00000011
            case 0x03 -> {
                signal.setName("反馈数据");
                signal.setText(getDataCommonDescription(signal.getCanData()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());
            }
            // 反馈终止
            // CODE=0xE4 11100100 XP => 0x1C800000 >>> 21
            // CODE=0xC8 11001000
            // CODE=0x64 01100100
            case 0x64 -> {
                signal.setName("反馈终止");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processActionTerminate(session, signal);
            }
            // 启动信号
            // CODE=0xA8 10101000 XP => 0x15000000 >>> 21
            // CODE=0x50 01010000
            // CODE=0x28 00101000
            case 0x28 -> {
                signal.setName("启动信号");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processStartup(session, signal);
            }
            // 停动信号
            // CODE=0xA9 10101001 XP => 0x15200000 >>> 21
            // CODE=0x52 01010010
            // CODE=0x29 00101001
            case 0x29 -> {
                signal.setName("停动信号");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processStartupStop(session, signal);
            }
            // 屏蔽信号
            // CODE=0xD4 11010100 XP => 0x1A800000 >>> 21
            // CODE=0x54 01010100
            // CODE=0x2A 00101010
            case 0x2A -> {
                signal.setName("屏蔽信号");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processShield(session, signal);
            }
            // 屏蔽取消
            // CODE=0xD6 11010110 XP => 0x1AC00000 >>> 21
            // CODE=0x56 01010110
            // CODE=0x2B 00101011
            case 0x2B -> {
                signal.setName("屏蔽取消");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processShieldCancel(session, signal);
            }
            // 故障信号
            // CODE=0xE1 11100001 XP => 0x1C200000 >>> 21
            // CODE=0xC2 11000010
            // CODE=0x61 01100001
            case 0x61 -> {
                signal.setName("故障信号");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processFault(session, signal);
            }
            // 故障数据
            // CODE=0x84 10000100 XP => 0x10800000 >>> 21
            // CODE=0x08 00001000
            // CODE=1x04 00000100
            case 0x04 -> {
                signal.setName("故障数据");
                signal.setText(getDataCommonDescription(signal.getCanData()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());
            }
            // 故障恢复
            // CODE=0xE5 11100101 XP => 1CA00000 >>> 21
            // CODE=0xCA 11001010
            // CODE=0x65 00000100
            case 0x65 -> {
                signal.setName("故障恢复");
                signal.setText(getEventCommonDescription(signal.getCanData()));
                signal.setDevCode(getCanDataDevCode(signal.getCanData()));
                signal.setDevType(getCanDataDevType(signal.getCanData()));
                signal.setDevTypeName(getCanDataDevTypeName(signal.getDevType()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processFaultRecover(session, signal);
            }
            // 时间同步
            // CODE=0xEA 11101010 XP => 0x1D400000 >>> 21
            // CODE=0xD4 11010100
            // CODE=0x6A 01101010
            case 0x6A -> {
                signal.setName("时间同步");
                signal.setText(getEventTimeDescription(signal.getCanData()));

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processHostTimeSync(session, signal);
            }
            // 主机心跳
            // CODE=0xF1 01110001 XP => 0x1E200000 >>> 21
            // CODE=0xE2 11100010
            // CODE=0x71 01110001
            case 0x71 -> {
                signal.setName("主机心跳");
                signal.setText("--");

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                // 由定时任务统一发送
                // processHostKeepalive(session);
            }
            // 未知指令
            default -> {
                signal.setName("未知指令");
                signal.setText("**");

                logInfo(signal, "%s[%s]", signal.getName(), signal.getText());

                processUnknown(session, signal);
            }
        }
    }

    public Signal createSignal(String lineData) {
        Signal signal = new Signal(lineData);
        signal.setCall(signal.getCanId().startsWith("1") ? 1 : 0);
        signal.setCode((Integer.parseInt(signal.getCanId().substring(1, 3), 16) >>> 1) & 0b1111111);
        signal.setMark((Integer.parseInt(signal.getCanId().substring(2, 4), 16) >>> 2) & 0b111);
        signal.setHost(Integer.parseInt(signal.getCanId().substring(4, 6), 16));
        signal.setSequence(Integer.parseInt(signal.getCanId().substring(6, 8), 16));
        signal.setPsCode(Integer.parseInt(signal.getCanId().substring(1, 3), 16));
        signal.setPiCode((Integer.parseInt(signal.getCanId().substring(0, 3), 16) >>> 1) & 0xFF);
        return signal;
    }

    /**
     * 获取事件的时间描述（心跳信号）
     *
     * @param hexString 十六进制字符串数据
     * @return 事件的时间描述
     */
    private String getEventTimeDescription(String hexString) {
        return getEventTimeDescription(hexStringToBytes(hexString));
    }

    /**
     * 获取事件的时间描述（心跳信号）
     *
     * @param bytes 字节数组
     * @return 事件的时间描述
     */
    private String getEventTimeDescription(byte[] bytes) {
        String month = bytesToBCDString(bytes, 2, 1);
        String day = bytesToBCDString(bytes, 3, 1);
        String hour = bytesToBCDString(bytes, 0, 1);
        String minute = bytesToBCDString(bytes, 1, 1);
        return "%s-%s %s:%s".formatted(month, day, hour, minute);
    }

    /**
     * 获取事件的状态描述（主机状态）
     *
     * @param hexString 十六进制字符串数据
     * @return 事件的状态描述
     */
    private long getEventStatusValue(String hexString) {
        return getEventStatusValue(hexStringToBytes(hexString));
    }

    /**
     * 获取事件的状态描述（主机状态）
     *
     * @param bytes 字节数组
     * @return 事件的状态描述
     */
    private long getEventStatusValue(byte[] bytes) {
        // 1、如果全部功能都处于禁止或关闭（手动禁止、自动禁止、喷洒禁止、防盗关闭）的情况下，将某一功能开启后再关闭或禁止，并不会发送关闭或禁止信号(PS: 也许是海湾1.2固件的BUG)
        // 2、【用户设置】->【打印控制】不发送信号
        // 3、【用户设置】->【启动控制】->【报警输出控制】不发送信号
        // 4、【用户设置】->【启动控制】->【消火栓设置】不发送信号
        // 5、【用户设置】->【防盗功能】部分发送信号: 关闭->开启会发送信号√、开启->关闭会发送信号√；开启->自动不发送信号×、自动->开启不发送信号×；关闭->自动不发送信号×、自动->关闭会发送信号√
        // 6、进入和退出调试模式不会发送特有的信号，但是进入时会发送15C001E7#0000000000000000，退出时需要复位

        // 0x20200000 0b00100000_00100000 手动禁止、自动禁止、喷洒允许、防盗关闭
        // 0x60200000 0b01100000_00100000 手动允许、自动禁止、喷洒允许、防盗关闭

        // 0x40200000 0b01000000_00100000 手动允许、自动禁止、喷洒禁止、防盗关闭
        // 0x60200000 0b01100000_00100000 手动允许、自动禁止、喷洒允许、防盗关闭

        // 0x60200000 0b01100000_00100000 手动允许、自动禁止、喷洒允许、防盗关闭
        // 0xE0300000 0b11100000_00110000 手动允许、部分自动、喷洒允许、防盗关闭
        // 0xE0400000 0b11100000_01000000 手动允许、全部自动、喷洒允许、防盗关闭

        // 0x60200000 0b01100000_00100000 手动允许、自动禁止、喷洒允许、防盗关闭
        // 0x20200000 0b00100000_00100000 手动禁止、自动禁止、喷洒允许、防盗关闭
        // 0x60200000 0b01100000_00100000 手动允许、自动禁止、喷洒允许、防盗关闭

        // 0xE0300000 0b11100000_00110000 手动允许、部分自动、喷洒允许、防盗关闭
        // 0xA0300000 0b10100000_00110000 手动禁止、部分自动、喷洒允许、防盗关闭
        // 0xE0300000 0b11100000_00110000 手动允许、部分自动、喷洒允许、防盗关闭

        // 0xE0400000 0b11100000_01000000 手动允许、全部自动、喷洒允许、防盗关闭
        // 0xA0400000 0b10100000_01000000 手动禁止、全部自动、喷洒允许、防盗关闭
        // 0xE0400000 0b11100000_01000000 手动允许、全部自动、喷洒允许、防盗关闭

        // 0x60200000 0b01100000_00100000 手动允许、自动禁止、喷洒允许、防盗关闭
        // 0x64200000 0b01100100_00100000 手动允许、自动禁止、喷洒允许、防盗开启
        return (bytes[0] & 0xFFL) << 8 | bytes[1] & 0xFFL;
    }

    /**
     * 获取事件的状态描述（主机状态）
     *
     * @param hexString 十六进制字符串数据
     * @return 事件的状态描述
     */
    private String getEventStatusDescription(String hexString) {
        return getEventStatusDescription(hexStringToBytes(hexString));
    }

    /**
     * 获取事件的状态描述（主机状态）
     *
     * @param bytes 字节数组
     * @return 事件的状态描述
     */
    private String getEventStatusDescription(byte[] bytes) {
        long hostStatus = getEventStatusValue(bytes);

        boolean manualForbid = (hostStatus & HostStatuses.MANUAL_MASK) == HostStatuses.MANUAL_FORBID;
        boolean manualPermit = (hostStatus & HostStatuses.MANUAL_MASK) == HostStatuses.MANUAL_PERMIT;

        boolean autoForbid = (hostStatus & HostStatuses.AUTO_MASK) == HostStatuses.AUTO_FORBID;
        boolean autoPartial = (hostStatus & HostStatuses.AUTO_MASK) == HostStatuses.AUTO_PARTIAL;
        boolean autoFull = (hostStatus & HostStatuses.AUTO_MASK) == HostStatuses.AUTO_FULL;

        boolean sprayForbid = (hostStatus & HostStatuses.SPRAY_MASK) == HostStatuses.SPRAY_FORBID;
        boolean sprayPermit = (hostStatus & HostStatuses.SPRAY_MASK) == HostStatuses.SPRAY_PERMIT;

        boolean guardDisable = (hostStatus & HostStatuses.GUARD_MASK) == HostStatuses.GUARD_DISABLE;
        boolean guardEnable = (hostStatus & HostStatuses.GUARD_MASK) == HostStatuses.GUARD_ENABLE;

        StringBuilder sb = new StringBuilder();
        if (manualForbid) {
            sb.append("手动禁止");
        } else {
            sb.append("手动允许");
        }
        sb.append(",");
        if (autoForbid) {
            sb.append("自动禁止");
        } else if (autoPartial) {
            sb.append("部分自动");
        } else {
            sb.append("全部自动");
        }
        sb.append(",");
        if (sprayForbid) {
            sb.append("喷洒禁止");
        } else {
            sb.append("喷洒允许");
        }
        sb.append(",");
        if (guardDisable) {
            sb.append("防盗关闭");
        } else {
            sb.append("防盗开启");
        }
        return sb.toString();
    }

    /**
     * 获取事件的通用描述
     *
     * @param hexString 十六进制字符串数据
     * @return 事件的通用描述
     */
    private String getEventCommonDescription(String hexString) {
        return getEventCommonDescription(hexStringToBytes(hexString));
    }

    /**
     * 获取事件的通用描述
     *
     * @param bytes 字节数组
     * @return 事件的通用描述
     */
    private String getEventCommonDescription(byte[] bytes) {
        // 设备编码（二次码）
        String devCode = getCanDataDevCodeStr(bytes);
        // 类型编码
        String devType = getCanDataDevTypeStr(bytes);
        // 类型名称
        String devTypeName = getCanDataDevTypeName(devType);
        // 描述信息
        return "二次码=%s, 类型编码=%s, 类型名称=%s".formatted(devCode, devType, devTypeName);
    }

    private int getCanDataDevCode(String hexString) {
        return Integer.parseInt(getCanDataDevCodeStr(hexStringToBytes(hexString)));
    }

    private int getCanDataDevCode(byte[] bytes) {
        return Integer.parseInt(getCanDataDevCodeStr(bytes));
    }

    private String getCanDataDevCodeStr(byte[] bytes) {
        if (bytes[0] == (byte) 0xAA && bytes[1] == (byte) 0xAA && bytes[2] == (byte) 0xAA) { // 讯响器是AAAAAA
            return "000000";
        } else { // 其他为BCD码6位数
            return bytesToBCDString(bytes, 0, 3);
        }
    }

    private int getCanDataDevType(String hexString) {
        return Integer.parseInt(getCanDataDevTypeStr(hexStringToBytes(hexString)));
    }

    private int getCanDataDevType(byte[] bytes) {
        return Integer.parseInt(getCanDataDevTypeStr(bytes));
    }

    private String getCanDataDevTypeStr(byte[] bytes) {
        return pointTableService.getStandardDevType(bytes[3] & 0xFF);
    }

    private String getCanDataDevTypeName(int type) {
        return getCanDataDevTypeName(pointTableService.getStandardDevType(type));
    }

    private String getCanDataDevTypeName(String typeStr) {
        String name = null;
        DeviceType deviceType = deviceTypeDao.selectByDeviceTypeId(Integer.parseInt(typeStr));
        if (deviceType != null) {
            name = deviceType.getDeviceTypeNewName();
        }
        if (name == null) {
            name = "未知";
        }
        return name;
    }

    /**
     * 获取数据的通用描述
     *
     * @param hexString 十六进制字符串数据
     * @return 数据的通用描述
     */
    private String getDataCommonDescription(String hexString) {
        return getDataCommonDescription(hexStringToBytes(hexString));
    }

    /**
     * 获取数据的通用描述
     *
     * @param bytes 字节数组
     * @return 数据的通用描述
     */
    private String getDataCommonDescription(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length / 2; i++) {
            byte b1 = bytes[i * 2];
            byte b2 = bytes[i * 2 + 1];
            // 第一个字节为0代表第二个字节是ASCII码
            if (b1 == 0) {
                // 过滤掉终止字符
                if (b2 == 0x00) {
                    continue;
                }
                sb.append((char) b2);
            }
            // 第一个字节不为0代表第一第二个字节合起来是个区位码
            else {
                String hexCode = String.format("%02X", Integer.parseInt(String.format("%02X", b1))) + String.format("%02X", Integer.parseInt(String.format("%02X", b2)));
                // 计算国标码（区位码的十六进制 + 0x2020）
                int gbCode = Integer.parseInt(hexCode, 16);
                String s2 = String.format("%04X", gbCode);
                gbCode += 0x2020;
                String s3 = String.format("%04X", gbCode);
                // 计算机内码（国标码 + 0x8080）
                int internalCode = gbCode + 0x8080;
                String s4 = String.format("%04X", internalCode);
                // 将机内码拆分为两个字节
                byte[] internalBytes = new byte[2];
                internalBytes[0] = (byte) ((internalCode >> 8) & 0xFF);
                internalBytes[1] = (byte) (internalCode & 0xFF);

                try {
                    sb.append(new String(internalBytes, "GBK").charAt(0));
                } catch (UnsupportedEncodingException e) {
                    sb.append("?");
                }
            }
        }
        // return StringUtils.stripEnd(sb.toString(), " "); // 过滤掉最后的空格再返回
        return sb.toString();
    }

    /**
     * 十六进制字符串数据转为字节数组
     *
     * @param hexString 十六进制字符串数据
     * @return 字节数组
     */
    private static byte[] hexStringToBytes(String hexString) {
        byte[] bytes = new byte[hexString.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
        }
        return bytes;
    }

    /**
     * 字节数组转为十六进制字符串数据
     *
     * @param bytes 字节数组
     * @return 十六进制字符串数据
     */
    public String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    /**
     * 字节数组转为BCD码字符串数据
     *
     * @param bytes 字节数组
     * @return BCD码字符串数据
     */
    public String bytesToBCDString(byte[] bytes) {
        return bytesToBCDString(bytes, 0, bytes.length);
    }

    /**
     * 字节数组转为BCD码字符串数据
     *
     * @param bytes  字节数组
     * @param offset 偏移量
     * @param length 长度
     * @return BCD码字符串数据
     */
    public String bytesToBCDString(byte[] bytes, int offset, int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            byte b = bytes[offset + i];
            sb.append(String.format("%d", b >>> 4 & 0b1111));
            sb.append(String.format("%d", b & 0b1111));
        }
        return sb.toString();
    }

    /**
     * 获取编码的字典键
     *
     * @param code 编码
     * @return 字符串字典键
     */
    private String getCodeKey(int code) {
        return "%02X".formatted(code);
    }

    /**
     * 获取主机的字典键
     *
     * @param host 主机
     * @return 字符串字典键
     */
    private String getHostKey(int host) {
        return "%03X".formatted(host);
    }


    /**
     * info日志输出
     *
     * @param signal GST200信号
     */
    private void logInfo(Signal signal) {
        logInfo(signal, null);
    }

    /**
     * info日志输出
     *
     * @param signal GST200信号
     * @param s      普通字符串内容或String.format格式字符串
     * @param args   String.format格式字符串参数
     */
    private void logInfo(Signal signal, String s, Object... args) {
        String tpl = "%s:[总线数据]: {}; CALL={},CODE=0x{},PS_CODE=0x{},PI_CODE=0x{},MARK={},HOST={},SEQ=0x{}".formatted(LogTags.getDtuServer());
        if (s == null) {
            log.info(
                    tpl,
                    signal.getLineData(),
                    signal.getCall(),
                    "%02X".formatted(signal.getCode()),
                    "%02X".formatted(signal.getPsCode()),
                    "%02X".formatted(signal.getPiCode()),
                    "%01d".formatted(signal.getMark()),
                    "%03d".formatted(signal.getHost()),
                    "%02X".formatted(signal.getSequence())
            );
        } else {
            if (args == null || args.length == 0) {
                tpl += " (" + s + ")";
                log.info(
                        tpl,
                        signal.getLineData(),
                        signal.getCall(),
                        "%02X".formatted(signal.getCode()),
                        "%02X".formatted(signal.getPsCode()),
                        "%02X".formatted(signal.getPiCode()),
                        "%01d".formatted(signal.getMark()),
                        "%03d".formatted(signal.getHost()),
                        "%02X".formatted(signal.getSequence())
                );
            } else {
                tpl += " (" + s.formatted(args) + ")";
                log.info(
                        tpl,
                        signal.getLineData(),
                        signal.getCall(),
                        "%02X".formatted(signal.getCode()),
                        "%02X".formatted(signal.getPsCode()),
                        "%02X".formatted(signal.getPiCode()),
                        "%01d".formatted(signal.getMark()),
                        "%03d".formatted(signal.getHost()),
                        "%02X".formatted(signal.getSequence()),
                        s.formatted(args)
                );
            }
        }
    }

    /**
     * 日志输出-warn
     *
     * @param signal GST200信号
     */
    private void logWarn(Signal signal) {
        logWarn(signal, null);
    }

    /**
     * 日志输出-warn
     *
     * @param signal GST200信号
     * @param s      普通字符串内容或String.format格式字符串
     * @param args   String.format格式字符串参数
     */
    private void logWarn(Signal signal, String s, Object... args) {
        String tpl = "%s:[总线数据]: {}; CALL={},CODE=0x{},PS_CODE=0x{},PI_CODE=0x{},MARK={},HOST={},SEQ=0x{}".formatted(LogTags.getDtuServer());
        if (s == null) {
            log.warn(
                    tpl,
                    signal.getLineData(),
                    signal.getCall(),
                    "%02X".formatted(signal.getCode()),
                    "%02X".formatted(signal.getPsCode()),
                    "%02X".formatted(signal.getPiCode()),
                    "%01d".formatted(signal.getMark()),
                    "%03d".formatted(signal.getHost()),
                    "%02X".formatted(signal.getSequence())
            );
        } else {
            if (args == null || args.length == 0) {
                tpl += " (" + s + ")";
                log.warn(
                        tpl,
                        signal.getLineData(),
                        signal.getCall(),
                        "%02X".formatted(signal.getCode()),
                        "%02X".formatted(signal.getPsCode()),
                        "%02X".formatted(signal.getPiCode()),
                        "%01d".formatted(signal.getMark()),
                        "%03d".formatted(signal.getHost()),
                        "%02X".formatted(signal.getSequence())
                );
            } else {
                tpl += " (" + s.formatted(args) + ")";
                log.warn(
                        tpl,
                        signal.getLineData(),
                        signal.getCall(),
                        "%02X".formatted(signal.getCode()),
                        "%02X".formatted(signal.getPsCode()),
                        "%02X".formatted(signal.getPiCode()),
                        "%01d".formatted(signal.getMark()),
                        "%03d".formatted(signal.getHost()),
                        "%02X".formatted(signal.getSequence())
                );
            }
        }
    }

    private void logPointTableRecordNotFound(HostSession session, Signal signal) {
        if (session == null) {
            log.warn("{}:[点表缺失]: 未找到dtu记录",
                    LogTags.getDtuServer()
            );
        } else {
            if (signal == null) {
                log.warn("{}:[点表缺失]: 主机地址【{}】, 主机类型【{}】",
                        LogTags.getDtuServer(),
                        session.getHostId(),
                        session.getDeviceType()
                );
            } else {
                log.warn("{}:[点表缺失]: 设备地址【{}】, 设备类型【{}】，类型名称【{}】",
                        LogTags.getDtuServer(),
                        pointTableService.getStandardDevCode(signal.getDevCode()),
                        pointTableService.getStandardDevType(signal.getDevType()),
                        signal.getDevTypeName()
                );
            }
        }
    }


    private void logDtuIdNotFound(HostSession hostSession, Signal signal) {
        if (pointTableService.isImported()) {
            log.warn("未查询到DTU_ID");
        } else {
            log.warn("点表尚未导入，请导入点表");
        }
    }

}
